﻿/*  ----------------------------------------------------------------------------
 *  SysServer
 *  ----------------------------------------------------------------------------
 *  File:       BufferPool.cs
 *  Creater:     SEEN\MistSeen
 *  ----------------------------------------------------------------------------
 *  $Date: 2010-09-06 10:36:01 +0000 (Mon, 06 Sep 2010) $
 *  $Rev: 15 $
 *  $Author: ScorDeity $
 *  $URL: http://sysserver.googlecode.com/svn/trunk/src/SysServer/Sock/BufferPool.cs $
 *  $Id: BufferPool.cs 15 2010-09-06 10:36:01Z ScorDeity $
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SysServer.Sock
{
    using SysServer.Collections.Generic;

    public sealed class BufferPool : DisposableEntity
    {
        private readonly TStack<byte[]> _allocatedSegments;
        private readonly TStack<ArraySegment<byte>> _childBuffers;
        private readonly int _chunkSize;
        private int _availableBuffers;
        private SpinWaitLock _changeLock;
        private int _totalChunks;

        internal BufferPool()
        {
        }

        /// <summary>
        /// Creates a new buffer pool, with the given number of initial buffers, at the given buffer size.
        /// </summary>
        /// <param name="bufferCount">the number of initial buffers to create</param>
        /// <param name="chunkSize">the size of the chunks</param>
        internal BufferPool(int bufferCount, int chunkSize)
        {
            if (bufferCount < 1)
                throw new ArgumentOutOfRangeException("bufferCount", Mouzetrap_Messages.BufferCountZero);

            if (chunkSize < 1)
                throw new ArgumentOutOfRangeException("chunkSize", Mouzetrap_Messages.ChunkSizeZero);

            _chunkSize = chunkSize;

            _allocatedSegments = new TStack<byte[]>();
            _childBuffers = new TStack<ArraySegment<byte>>();

            int initialNeededSegment = MathHelper.GetNearestPowerOfTwo(bufferCount * chunkSize);
            AllocateSegment(initialNeededSegment);
        }

        /// <summary>
        /// The total number of buffers registered with this pool.
        /// </summary>
        public int TotalBuffers
        {
            get
            {
                if (IsDisposed)
                    throw new ObjectDisposedException("buffer pool");

                return _totalChunks;
            }
        }

        /// <summary>
        /// The number of available buffers in the pool.
        /// </summary>
        public int AvailableBuffers
        {
            get
            {
                if (IsDisposed)
                    throw new ObjectDisposedException("buffer pool");

                return _availableBuffers;
            }
        }

        /// <summary>
        /// The size of chunks given by this pool.
        /// </summary>
        public int BufferSize
        {
            get
            {
                if (IsDisposed)
                    throw new ObjectDisposedException("buffer pool");

                return _chunkSize;
            }
        }

        /// <summary>
        /// Grabs a buffer from the pool.
        /// </summary>
        /// <remarks>
        /// If the pool is empty, more buffers will be created and added to the pool to service the request.
        /// </remarks>
        /// <returns>a buffer pointing to a chunk of a larger memory segment</returns>
        public ArraySegment<byte> GetBuffer()
        {
            if (IsDisposed)
                throw new ObjectDisposedException("buffer pool");

            ArraySegment<byte> buf;

            if (!_childBuffers.Pop(out buf))
            {
                _changeLock.Enter();

                try
                {
                    while (!_childBuffers.Pop(out buf))
                    {
                        IncreaseCapacity();
                    }
                }
                finally
                {
                    _changeLock.Exit();
                }
            }

            _availableBuffers--;

            return buf;
        }

        /// <summary>
        /// Releases a buffer back to the pool.
        /// </summary>
        /// <param name="buffer">the buffer to release</param>
        public void ReturnBuffer(ArraySegment<byte> buffer)
        {
            if (IsDisposed)
                throw new ObjectDisposedException("buffer pool");

            _childBuffers.Push(buffer);

            _availableBuffers++;
        }

        private void IncreaseCapacity()
        {
            // we only increase capacity when we create the pool, or can't serve a request (starvation)
            // so we use the figure of 5% as how much we'll increase our overall capacity.
            int neededSegmentSize = (((int)(_totalChunks * 0.05)) * _chunkSize);

            // handles low buffer count situations where 'neededSegmentSize' may truncate to 0
            // and will yield atleast 1 child chunk
            if (neededSegmentSize < _chunkSize)
            {
                neededSegmentSize = _chunkSize;
            }

            int roundedSegmentSize = MathHelper.GetNearestPowerOfTwo(neededSegmentSize);

            AllocateSegment(roundedSegmentSize);
        }

        private void AllocateSegment(int segmentSize)
        {
            var newSegment = new byte[segmentSize];

            // create the child buffers
            for (int offset = 0; offset < segmentSize; offset += _chunkSize)
            {
                // for cases when the chunk size isn't a power of two.
                if (offset + _chunkSize > segmentSize)
                    break;

                _childBuffers.Push(new ArraySegment<byte>(newSegment, offset, _chunkSize));
            }

            // get the total chunks created here, and add that to our total count and available count
            int newChunks = (segmentSize / _chunkSize);

            _totalChunks += newChunks;
            _availableBuffers += newChunks;

            _allocatedSegments.Push(newSegment);
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);

            if (disposing)
            {
                // clear child buffers
                ArraySegment<byte> buf;
                while (_childBuffers.Pop(out buf)) ;

                // dispose/clear segment chunks
                byte[] segment;
                while (_allocatedSegments.Pop(out segment)) ;
            }
        }
    }

}